function [squaresTree, squaresList] = configureSquaresTask
%Configure Snow Dots demo task
%
%   [squaresTree, squaresList] = configureSquaresTask
%
%   squaresTree is a topsTreeNode object which organizes tasks and trials.
%   squaresTree.run() will start the "squares" demo.  squaresTree.gui()
%   will launch a graphical interface for viewing the organization of the
%   demo.
%
%   squaresList is a topsGroupedList object which holds all the data needed
%   to run the "squares" experiment.  squaresList.gui() will launch a
%   graphical interface for viewing the objects and data used in the demo.
%
%   There is one task in the squares demo, and repeated trials for that
%   task.  It's a reaction time (rt) in which the subject (you) uses the
%   mouse to touch a cursor to one of several squares that appear.
%
%   Each trial goes like this:
%       -several red squares and one blue square appear on the screen.
%       -at any time, the subject may move the mouse cursor to toucn one of
%       the squares.  Only the blue square is "correct".
%       -the squares disappear and the screen is blank for an interval.
%       -the next trial begins...
%
%   Several experiment parameters may be controlled by editing values near
%   the top of this file, configureSquaresTasks.m.
%
% 2009 benjamin.heasly@gmail.com
%   Seattle, WA

%%%
%%% experiment parameters to edit:
%%%
squareRows = 6;
squareColumns = 6;
squareCount = 8;
squarePixelSize = 50;
squareViewingTime = .25;

intertrialInterval = 1;
trialsInARow = 5;

taskRepetitions = 2;
taskOrder = 'random'; % 'sequential' or 'random'


%%%
%%% foundataion classes
%%%

% topsGroupedList
% squaresList list will hold all parameters and other data for the squares
squaresList = topsGroupedList;
squaresList.addItemToGroupWithMnemonic(squareRows, 'graphics', 'squareRows');
squaresList.addItemToGroupWithMnemonic(squareColumns, 'graphics', 'squareColumns');
squaresList.addItemToGroupWithMnemonic(squareCount, 'graphics', 'squareCount');
squaresList.addItemToGroupWithMnemonic(squarePixelSize, 'graphics', 'squarePixelSize');
squaresList.addItemToGroupWithMnemonic(squareViewingTime, 'graphics', 'squareViewingTime');

squaresList.addItemToGroupWithMnemonic(intertrialInterval, 'control', 'intertrialInterval');
squaresList.addItemToGroupWithMnemonic(trialsInARow, 'control', 'trialsInARow');
squaresList.addItemToGroupWithMnemonic(taskRepetitions, 'control', 'taskRepetitions');

% initialize the manager which will create and handle graphics objects
dotsTheDrawablesManager.reset;
drawManager = dotsTheDrawablesManager.theObject;

% make dotsDrawableTargets objects, which can draw circles or squares
redSquares = drawManager.newObject('dotsDrawableTargets');
redSquares.isVisible = false;
redSquares.color = [192 0 32];
redSquares.size = squarePixelSize;
redSquares.shape = 0;
squaresList.addItemToGroupWithMnemonic(redSquares, 'graphics', 'redSquares');

blueSquare = drawManager.newObject('dotsDrawableTargets');
blueSquare.color = [32 0 192];
blueSquare.size = squarePixelSize;
blueSquare.shape = 0;
squaresList.addItemToGroupWithMnemonic(blueSquare, 'graphics', 'blueSquare');

cursor = drawManager.newObject('dotsDrawableTargets');
cursor.color = [192 192 128];
cursor.size = 5;
cursor.shape = 1;
squaresList.addItemToGroupWithMnemonic(cursor, 'graphics', 'cursor');

% tell the manager to draw these objects as a group
%   the last argument allows the objects to be sorted by drawing order
drawManager.addObjectToGroup(redSquares, 'squares', 1);
drawManager.addObjectToGroup(blueSquare, 'squares', 2);
drawManager.addObjectToGroup(cursor, 'squares', 3);
drawManager.activateGroup('squares');

% initialize the queryable that will manage mouse inputs
%   define the plane of interest in degrees visual angle
%       classified in half degrees of visual angle
%       with arbitrary, guessed-at scaling of physical mouse movement
%       with inverted y-axis to match Snow Dots graphics coordinates
mouse = dotsQueryableHIDMouse;
squaresList.addItemToGroupWithMnemonic(mouse, 'control', 'mouse');
if mouse.isAvailable
    mouse.xyRect = [-10 -10 20 20];
    mouse.xyPoints = [100 100];
    mouse.xyScale = [10 -10];
    mouse.initialize;
    
    % define a classificatinon for the mouse "phenomenon" of clicking button 1
    %   this is the default, incorrect response
    %   the correct response, clicking on the blue square specifically,
    %       is defined below in rtPretrialSetup and rtTrialSetup
    click = mouse.phenomenons.getItemFromGroupWithMnemonic('pressed', 'pressed_button_1');
    mouse.addClassificationInGroupWithRank(click, 'incorrect', 'squares', 1);
end
% topsCallList
% Call lists can call multiple functions in batches
%   this helps keep multiple things updated together during a trial
squaresCalls = topsCallList;
squaresCalls.alwaysRunning = true;
squaresList.addItemToGroupWithMnemonic(squaresCalls, 'control', 'squaresCalls');

% want to keep drawing, during a trial
%   check whether the subject clicked the blue square
%   update the cursor from the mouse
%   draw the next frame and wait for the screen "flip"
squaresCalls.addCall({@updateCursor, cursor, mouse});
squaresCalls.addCall({@checkClick, mouse, squaresCalls});
squaresCalls.addCall({@runBriefly, drawManager});

% topsTreeNode
% squaresTree manages the main window
%   it also will have the rt task as its "children"
squaresTree = topsTreeNode;
squaresTree.name = 'squares';
squaresTree.iterations = taskRepetitions;
squaresTree.iterationMethod = taskOrder;
squaresTree.startFevalable = {@mainSetup, squaresList};
squaresTree.finishFevalable = {@mainTearDown, squaresList};
squaresList.addItemToGroupWithMnemonic(squaresTree, 'control', 'top-level');

% rtTask manages the reaction time task
%   it will also have a reaction time *trial* as its child
taskName = 'rt_task';
rtTask = squaresTree.newChildNode;
rtTask.name = taskName;
rtTask.iterations = trialsInARow;
rtTask.startFevalable = {@rtTaskSetup, squaresList};
rtTask.finishFevalable = {@rtTaskTearDown, squaresList};
squaresList.addItemToGroupWithMnemonic(rtTask, 'control', 'rtTask');

% rtpreTrial requires preceeds actual reaction time trials
rtPretrial = rtTask.newChildNode;
rtPretrial.name = 'rt_pretrial';
rtPretrial.startFevalable = {@rtPretrialSetup, squaresList};
rtPretrial.addChild(squaresCalls);
rtPretrial.finishFevalable = {@rtPretrialTeardown, squaresList};
squaresList.addItemToGroupWithMnemonic(rtPretrial, 'control', 'rtPretrial');

% rtTrial manages individual reaction time trials
rtTrial = rtTask.newChildNode;
rtTrial.name = 'rt_trial';
rtTrial.startFevalable = {@rtTrialSetup, squaresList};
rtTrial.addChild(squaresCalls);
rtTrial.finishFevalable = {@rtTrialTeardown, squaresList};
squaresList.addItemToGroupWithMnemonic(rtTrial, 'control', 'rtTrial');

%%%
%%% Functions for the overall experiment (the top level)
%%%
function updateCursor(cursor, mouse)
% try to update the cursor once, before each frame
drawManager = dotsTheDrawablesManager.theObject;
if ~isempty(drawManager.lastFrameTime)
    mouse.readData;
    cursor.x = mouse.xPos;
    cursor.y = mouse.yPos;
    drawManager.mayDrawNextFrame;
end

function checkClick(mouse, squaresCalls)
output = mouse.queryAsOfTime;
if ~isempty(output)
    if isequal(output, mouse.unavailableOutput)
        % bail out on no mouse
        squaresCalls.isRunning = false;
    else
        topsDataLog.logDataInGroup(output, 'clicked mouse');
        if strcmp(output, 'correct')
            % finish the trial
            squaresCalls.isRunning = false;
        end
    end
end

function mainSetup(squaresList)
% open a drawing window
drawManager = dotsTheDrawablesManager.theObject;
drawManager.openScreenWindow;
drawManager.mayDrawNextFrame;

function mainTearDown(squaresList)
% close the drawing window
drawManager = dotsTheDrawablesManager.theObject;
drawManager.closeScreenWindow;

%%%
%%% Functions for the rt task (a middle level)
%%%
function rtTaskSetup(squaresList)
% build stimulus squares
n = squaresList.getItemFromGroupWithMnemonic('graphics', 'squareCount');
r = squaresList.getItemFromGroupWithMnemonic('graphics', 'squareRows');
c = squaresList.getItemFromGroupWithMnemonic('graphics', 'squareColumns');
shuffle = randperm(r*c);

drawManager = dotsTheDrawablesManager.theObject;
area = drawManager.windowRect/drawManager.pixelsPerDegree;
area(1) = area(1)-area(3)/2;
area(2) = area(2)-area(4)/2;
for ii = 1:n
    m = shuffle(ii);
    pos = subposition(area, r, c, mod(m,c)+1, ceil(m/c));
    x(ii) = pos(1) + pos(3)/2;
    y(ii) = pos(2) + pos(4)/2;
end
squaresList.addItemToGroupWithMnemonic(x, 'random', 'square x positions');
squaresList.addItemToGroupWithMnemonic(y, 'random', 'square y positions');

topsDataLog.logDataInGroup(x, 'square x positions');
topsDataLog.logDataInGroup(y, 'square y positions');

function rtTaskTearDown(squaresList, groupName)

%%%
%%% Functions for the rt pretrial (a middle level)
%%%
function rtPretrialSetup(squaresList)
% access a bunch of graphics information
cursor = squaresList.getItemFromGroupWithMnemonic('graphics', 'cursor');
blueSquare = squaresList.getItemFromGroupWithMnemonic('graphics', 'blueSquare');
squarePixelSize = squaresList.getItemFromGroupWithMnemonic('graphics', 'squarePixelSize');

squareDegSize = squarePixelSize/blueSquare.pixelsPerDegree;
blueSquare.x = 0;
blueSquare.y = 0;

% record the new postion of the blue square
blueRect = [ ...
    blueSquare.x-squareDegSize/2, ...
    blueSquare.y-squareDegSize/2, ...
    squareDegSize, ...
    squareDegSize];
topsDataLog.logDataInGroup(blueRect, 'blue square rect');

% blue square, once the pretrial starts
cursor.isVisible = true;
blueSquare.isVisible = true;

% set up a new mouse phenomenon
%   for the correct response of clicking on the blue square
mouse = squaresList.getItemFromGroupWithMnemonic('control', 'mouse');
if mouse.isAvailable
    click = mouse.phenomenons.getItemFromGroupWithMnemonic('pressed', 'pressed_button_1');
    temp = mouse.getPhenomenonTemplate;
    inBlue = dotsPhenomenon.rectangle(temp, 'xPos', 'yPos', blueRect, 'in');
    clickInBlue = dotsPhenomenon.composite([click inBlue], 'intersection');
    mouse.addClassificationInGroupWithRank(clickInBlue, 'correct', 'squares', 2);
    mouse.activeClassificationGroup = 'squares';
end

% dump old mouse data
mouse.flushData;

function rtPretrialTeardown(squaresList)
% hide all squares
dotsTheSwitchboard.setSharedPropertyValue('isVisible', false);
drawManager = dotsTheDrawablesManager.theObject;
drawManager.mayDrawNextFrame;


%%%
%%% Functions for the rt trial (a bottom level)
%%%
function rtTrialSetup(squaresList)
% access a bunch of graphics information
redSquares = squaresList.getItemFromGroupWithMnemonic('graphics', 'redSquares');
blueSquare = squaresList.getItemFromGroupWithMnemonic('graphics', 'blueSquare');
squarePixelSize = squaresList.getItemFromGroupWithMnemonic('graphics', 'squarePixelSize');

x = squaresList.getItemFromGroupWithMnemonic('random', 'square x positions');
y = squaresList.getItemFromGroupWithMnemonic('random', 'square y positions');

% pick a random position for the blue square
n = length(x);
order = randperm(n);
redSquares.x = x(order(2:n));
redSquares.y = y(order(2:n));
blueSquare.x = x(order(1));
blueSquare.y = y(order(1));

% record the new postion of the blue square
squareDegSize = squarePixelSize/blueSquare.pixelsPerDegree;
blueRect = [ ...
    blueSquare.x-squareDegSize/2, ...
    blueSquare.y-squareDegSize/2, ...
    squareDegSize, ...
    squareDegSize];
topsDataLog.logDataInGroup(blueRect, 'blue square rect');

% show all squares, once the trial starts
dotsTheSwitchboard.setSharedPropertyValue('isVisible', true);

% set up a new mouse phenomenon
%   for the correct response of clicking on the blue square
mouse = squaresList.getItemFromGroupWithMnemonic('control', 'mouse');
if mouse.isAvailable
    click = mouse.phenomenons.getItemFromGroupWithMnemonic('pressed', 'pressed_button_1');
    temp = mouse.getPhenomenonTemplate;
    inBlue = dotsPhenomenon.rectangle(temp, 'xPos', 'yPos', blueRect, 'in');
    clickInBlue = dotsPhenomenon.composite([click inBlue], 'intersection');
    mouse.addClassificationInGroupWithRank(clickInBlue, 'correct', 'squares', 2);
    mouse.activeClassificationGroup = 'squares';
end

% dump old mouse data
mouse.flushData;

function rtTrialTeardown(squaresList)
% hide all squares
dotsTheSwitchboard.setSharedPropertyValue('isVisible', false);
drawManager = dotsTheDrawablesManager.theObject;
drawManager.mayDrawNextFrame;

% wait out the iti, pathetically for now
iti = squaresList.getItemFromGroupWithMnemonic('control', 'intertrialInterval');
pause(iti);
